<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router'
import dayjs from 'dayjs'
import type { TablePaginationConfig } from 'ant-design-vue'
import { Modal, message } from 'ant-design-vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { computed, h, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { statusValueMap } from './config'
import TaskStatus from './components/TaskStatus.vue'
import TaskModal from './components/TaskModal.vue'
import PageWrapper from '@/components/PageWrapper.vue'
import type { DistributionParams } from '@/api/paths/types'
import {
  api,
} from '@/api'
import type {
  CveFixInfo,
  CveRollbackInfo,
  HotPatchInfo,
  RepoSetInfo,
  Rpm,
  TaskDetail,
} from '@/api'

const tableRef = ref()
const { t } = useI18n()
type TaskType = 'cve fix' | 'cve rollback' | 'repo set' | 'hotpatch remove' | ''

const taskTypeEnum = computed(() => ({
  'cve fix': t('vul.task.taskTypeEnum.cveFix'),
  'repo set': t('vul.task.taskTypeEnum.repoSet'),
  'cve rollback': t('vul.task.taskTypeEnum.cveRollback'),
  'hotpatch remove': t('vul.task.taskTypeEnum.hotPatchRemove'),
}))

const RepoSetStatusEnum = {
  succeed: t('vul.task.taskStatus.repoSucceed'),
  fail: t('vul.task.taskStatus.repoFailed'),
  running: t('vul.task.taskStatus.repoRunning'),
  unknown: t('vul.task.taskStatus.unknown'),
}

const CveFixStatusEnum = {
  succeed: t('vul.task.taskStatus.fixSucceed'),
  fail: t('vul.task.taskStatus.fixFailed'),
  running: t('vul.task.taskStatus.fixRunning'),
  unknown: t('vul.task.taskStatus.unknown'),
}

const CveRollbackStatusEnum = {
  succeed: t('vul.task.taskStatus.rollbacklSucceed'),
  fail: t('vul.task.taskStatus.rollbackFailed'),
  running: t('vul.task.taskStatus.rollbackRunning'),
  unknown: t('vul.task.taskStatus.unknown'),
}

const HotPatchRemoveStatusEnum = {
  succeed: t('vul.task.taskStatus.removeSucceed'),
  fail: t('vul.task.taskStatus.removeFailed'),
  running: t('vul.task.taskStatus.removeRunning'),
  unknown: t('vul.task.taskStatus.unknown'),
}

const statusEnumMap: { [key: string]: any } = {
  'cve fix': CveFixStatusEnum,
  'cve rollback': CveRollbackStatusEnum,
  'repo set': RepoSetStatusEnum,
  'hotpatch remove': HotPatchRemoveStatusEnum,
}

const route = useRoute()
const router = useRouter()

const taskDetail = reactive<{
  isInfoLoading: boolean
  info: TaskDetail
  taskType: TaskType
  taskId: string
}>({
  isInfoLoading: false,
  info: {
    task_name: '',
    accept: false,
    takeover: false,
    description: '',
    host_num: 0,
    cluster_id: '',
    cluster_name: '',
    latest_execute_time: 0,
  },
  taskType: '',
  taskId: '',
})

function toReport() {
  router.push({
    path: '/vulnerability/tasks/task-report',
    query: {
      taskId: taskDetail.taskId,
      taskType: taskDetail.taskType,
      clusterId: taskDetail.info.cluster_id,
    },
  })
}

// #region --------------------------------------< table >--------------------------------------
const taskTableColumns = computed(() => {
  switch (taskDetail.taskType) {
    case 'hotpatch remove':
      return [
        {
          dataIndex: 'cve_id',
          title: 'CVE ID',
        },
        {
          dataIndex: 'host_num',
          title: t('vul.cves.hosts'),
        },
        {
          dataIndex: 'package',
          title: t('vul.task.package'),
        },
        {
          dataIndex: 'progress',
          title: t('vul.task.progress'),
          width: 300,
        },
        {
          dataIndex: 'status',
          title: t('vul.task.removeStatus'),
          filters: [
            { text: t('vul.task.taskStatus.removeSucceed'), value: 'succeed' },
            { text: t('vul.task.taskStatus.removeFailed'), value: 'fail' },
            { text: t('vul.task.taskStatus.removeRunning'), value: 'running' },
            { text: t('vul.task.taskStatus.unknown'), value: 'unknown' },
          ],
        },
      ]
    case 'cve fix':
      return [
        {
          dataIndex: 'host_name',
          title: t('vul.hostName'),
        },
        {
          dataIndex: 'host_ip',
          title: t('vul.ip'),
        },
        {
          dataIndex: 'cve_num',
          title: t('vul.task.cveCount'),
        },
        {
          dataIndex: 'status',
          title: t('vul.task.fixStatus'),
          filters: [
            { text: t('vul.task.taskStatus.fixSucceed'), value: 'succeed' },
            { text: t('vul.task.taskStatus.fixFailed'), value: 'fail' },
            { text: t('vul.task.taskStatus.fixRunning'), value: 'running' },
            { text: t('vul.task.taskStatus.unknown'), value: 'unknown' },
          ],
        },
      ]
    case 'cve rollback':
      return [
        {
          dataIndex: 'host_name',
          title: t('vul.hostName'),
        },
        {
          dataIndex: 'host_ip',
          title: t('vul.ip'),
        },
        {
          dataIndex: 'cve_num',
          title: t('vul.task.cveCount'),
        },
        {
          dataIndex: 'status',
          title: t('vul.task.rollbackStatus'),
          filters: [
            { text: t('vul.task.taskStatus.rollbacklSucceed'), value: 'succeed' },
            { text: t('vul.task.taskStatus.rollbackFailed'), value: 'fail' },
            { text: t('vul.task.taskStatus.rollbackRunning'), value: 'running' },
            { text: t('vul.task.taskStatus.unknown'), value: 'unknown' },
          ],
        },
      ]
    case 'repo set':
      return [
        {
          dataIndex: 'host_name',
          title: t('vul.hostName'),
        },
        {
          dataIndex: 'host_ip',
          title: t('vul.ip'),
        },
        {
          dataIndex: 'repo_name',
          title: 'CVE REPO',
        },
        {
          dataIndex: 'status',
          title: t('vul.task.uptodate'),
          filters: [
            { text: t('vul.task.taskStatus.repoSucceed'), value: 'succeed' },
            { text: t('vul.task.taskStatus.repoFailed'), value: 'fail' },
            { text: t('vul.task.taskStatus.repoRunning'), value: 'running' },
            { text: t('vul.task.taskStatus.unknown'), value: 'unknown' },
          ],
        },
      ]
    default:
      return []
  }
})
const tableData = ref<CveFixInfo[] | HotPatchInfo[] | CveRollbackInfo[] | RepoSetInfo[]>([])
const isTableLoading = ref(false)
const searchKey = ref<string>()
const filterMap = reactive<{
  status?: string[]
}>({})

const pagination = reactive({
  total: 0,
  current: 1,
  pageSize: 10,
  showTotal: (total: number) => t('common.total', { count: total }),
  showSizeChanger: true,
  pageSizeOptions: ['10', '20', '30', '40'],
})

const tableState = reactive<{
  expandedRowKeys: string[]
}>({
  expandedRowKeys: [],
})

let hostListTimer: NodeJS.Timer | null
async function queryHostInTask(taskId: string) {
  let isRunning = false

  const dealTableInfo = (info: any, callback: () => void) => {
    isRunning = info.some(item => item.status === 'running')
    if (isRunning) {
      hostListTimer = setTimeout(() => {
        callback()
      }, 5000)
    }
    else {
      isRunning = false
      hostListTimer && clearTimeout(hostListTimer)
    }
  }

  /**
   * query cve fix info
   */
  const queryCveFixInfo = async () => {
    const [, res] = await api.getCveFixInfo({
      page: pagination.current,
      per_page: pagination.pageSize,
      task_id: taskId,
      filter: Object.assign(filterMap, { search_key: searchKey.value || undefined }) || {},
    })
    if (res) {
      const originData = JSON.parse(JSON.stringify(tableData.value))
      tableData.value = res.result.map((item) => {
        const ind = (originData as CveFixInfo[]).findIndex(h => h.host_id === item.host_id)
        if (ind > -1) {
          const result = { ...originData[ind], ...item }
          if (result.status !== 'running' && tableState.expandedRowKeys.includes(result.host_id))
            queryRpms(true, tableData.value[ind] as CveFixInfo)
          return result
        }
        else {
          return item
        }
      })
      pagination.total = res.total_count
      dealTableInfo(tableData.value, queryCveFixInfo)
    }
  }
  /**
   * query hotpatch remove info
   */
  const queryHotPatchInfo = async () => {
    const [, res] = await api.getHotPatchInfo({
      page: pagination.current,
      per_page: pagination.pageSize,
      task_id: taskId,
      filter: Object.assign(filterMap, { cve_id: searchKey.value || undefined }) || {},
    })
    if (res) {
      tableData.value = res.result
      pagination.total = res.total_count
      dealTableInfo(tableData.value, queryHotPatchInfo)
    }
  }
  /**
   * query cve rollback info
   */
  const queryCveRollbackInfo = async () => {
    const [, res] = await api.getCveRollbackInfo({
      page: pagination.current,
      per_page: pagination.pageSize,
      task_id: taskId,
      filter: Object.assign(filterMap, { search_key: searchKey.value || undefined }) || {},
    })
    if (res) {
      const originData = JSON.parse(JSON.stringify(tableData.value))
      tableData.value = res.result.map((item) => {
        const ind = (originData as CveRollbackInfo[]).findIndex(h => h.host_id === item.host_id)
        if (ind > -1) {
          const result = { ...originData[ind], ...item }
          if (result.status !== 'running' && tableState.expandedRowKeys.includes(result.host_id))
            queryRpms(true, tableData.value[ind] as CveRollbackInfo)
          return result
        }
        else {
          return item
        }
      })
      pagination.total = res.total_count
      dealTableInfo(tableData.value, queryCveRollbackInfo)
    }
  }
  /**
   * query repo info
   */
  const queryRepoSetInfo = async () => {
    const [, res] = await api.getRepoSetInfo({
      page: pagination.current,
      per_page: pagination.pageSize,
      task_id: taskId,
      filter: Object.assign(filterMap, { host_id: searchKey.value || undefined }) || {},
    })
    if (res) {
      tableData.value = res.result
      pagination.total = res.total_count
      dealTableInfo(tableData.value, queryRepoSetInfo)
    }
  }

  const { taskType } = taskDetail

  isTableLoading.value = true
  if (taskType === 'cve fix')
    await queryCveFixInfo()
  else if (taskType === 'hotpatch remove')
    await queryHotPatchInfo()
  else if (taskType === 'cve rollback')
    await queryCveRollbackInfo()
  else
    await queryRepoSetInfo()

  isTableLoading.value = false
}

const expandTableColumns = computed(() => {
  const { taskType } = taskDetail
  return [
    {
      dataIndex: taskType === 'cve rollback' ? 'installed_rpm' : 'installed_rpm',
      title: taskType === 'cve rollback' ? t('vul.task.installedRpm') : t('vul.task.affectedRpm'),
    },
    {
      dataIndex: taskType === 'cve rollback' ? 'target_rpm' : 'available_rpm',
      title: taskType === 'cve rollback' ? t('vul.task.targetRpm') : t('vul.task.targetRpm'),
    },
    {
      dataIndex: 'cves',
      title: 'CVE',
    },
    {
      dataIndex: 'status',
      title: taskType === 'cve rollback' ? t('vul.task.rollbackStatus') : t('vul.task.fixStatus'),
    },
  ]
})
let rpmTimer: NodeJS.Timer | null
async function queryRpms(expanded: boolean, record: CveFixInfo | CveRollbackInfo) {
  if (!expanded || !taskDetail.taskId)
    return
  // const isRunning = false
  const { taskType } = taskDetail
  /**
   * query cve fix rpms
   */
  const queryCveFixRpm = async (taskId: string, hostId: string) => {
    const [, res] = await api.getCveFixRpm(hostId, taskId)
    if (res) {
      const host = (tableData.value as CveFixInfo[] | CveRollbackInfo[]).find(host => hostId === host.host_id)!
      host.rpms = res
    }
  }
  /**
   * query cve rollback rpms
   */
  const queryCveRollbackRpm = async (taskId: string, hostId: string) => {
    const [, res] = await api.getCveRollbackRpm(record.host_id, taskId)
    if (res) {
      const host = (tableData.value as CveRollbackInfo[]).find(host => hostId === host.host_id)!
      host.rpms = res
    }
  }
  if (taskType === 'cve fix')
    await queryCveFixRpm(taskDetail.taskId, record.host_id)
  else if (taskType === 'cve rollback')
    await queryCveRollbackRpm(taskDetail.taskId, record.host_id)
}

const cutOff = computed(() => (originStr: string) => `${originStr.split(',')[0]}...`)

// #endregion

const isTaskRunning = ref(false)
let detailTimer: NodeJS.Timer | null
/**
 * query host info
 * @param taskId
 */
async function queryTaskDetailInfo(taskId: string) {
  const scanHost = async () => {
    const params: DistributionParams<{ host_list: string[] }> = {}
    params[taskDetail.info.cluster_id] = {
      host_list: (tableData.value as CveFixInfo[] | CveRollbackInfo[]).map(item => item.host_id),
    }
    await api.scanHost(params)
  }

  const queryTasksStatus = async (taskId: string) => {
    const [, res] = await api.getTasksStatus([taskId])
    if (res) {
      taskDetail.info.status = res.result[taskId]
      if (taskDetail.info.status.running) {
        isTaskRunning.value = true
        detailTimer = setTimeout(() => {
          queryTasksStatus(taskId)
        }, 5000)
      }
      else {
        if (isTaskRunning.value && (taskDetail.taskType === 'cve fix' || taskDetail.taskType === 'cve rollback'))
          scanHost()
        isTaskRunning.value = false
        detailTimer && clearTimeout(detailTimer)
      }
    }
  }
  taskDetail.isInfoLoading = true
  const [, res] = await api.getTaskDetailInfo(taskId)
  if (res) {
    taskDetail.info = res
    await queryTasksStatus(taskId)
  }
  taskDetail.isInfoLoading = false
}

/**
 * execute task
 */
async function handleExecuteTask() {
  if (isTaskRunning.value)
    return
  Modal.confirm({
    title: t('vul.task.sentence.executeConfirm', [taskDetail.info.task_name]),
    icon: h(ExclamationCircleOutlined),
    okType: 'primary',
    okText: t('common.confirm'),
    onOk: async () => {
      try {
        isTaskRunning.value = true
        const { taskId } = taskDetail
        const { cluster_id } = taskDetail.info
        const params: DistributionParams<{ task_id: string }> = {}
        params[cluster_id] = { task_id: taskId }
        const [_] = await api.executeTask(params)
        if (!_) {
          tableState.expandedRowKeys = []
          message.success(t('common.succeed'))
          queryTaskDetailInfo(taskId)
          queryHostInTask(taskId)
        }
      }
      catch {}
    },
  })
}
const isGenerating = ref(false)
const isSuccessModalVisible = ref(false)

/**
 * generate rollback task
 */
async function handleGenerateRollback() {
  if (isTaskRunning.value) {
    message.warning(t('vul.task.message.taskIsExecuting'))
    return
  }
  Modal.confirm({
    title: t('vul.task.sentence.rollbackConfirm'),
    icon: h(ExclamationCircleOutlined),
    okType: 'primary',
    okText: t('common.confirm'),
    onOk: async () => {
      try {
        isGenerating.value = true
        const params: DistributionParams<{ fix_task_id: string }> = {}
        const { cluster_id } = taskDetail.info
        params[cluster_id] = { fix_task_id: taskDetail.taskId }
        const [_] = await api.generateCveRollbackTask(params)
        if (!_)
          isSuccessModalVisible.value = true
      }
      catch {
      }
      finally {
        isGenerating.value = false
      }
    },
  })
}

const searchPlaceholder = computed(() =>
  taskDetail.taskType === 'hotpatch remove' ? t('vul.task.placeHolder.searchByCve') : t('vul.task.placeHolder.searchByHost'),
)

function handleTableChange(page: TablePaginationConfig, filters: Record<string, string[] | null>) {
  tableState.expandedRowKeys = []
  page.current && (pagination.current = page.current)
  page.pageSize && (pagination.pageSize = page.pageSize)
  filterMap.status = filters.status ?? []
  queryHostInTask(taskDetail.taskId)
}

function handleSearch() {
  tableState.expandedRowKeys = []
  queryHostInTask(taskDetail.taskId)
}

function clearTimer() {
  hostListTimer && clearTimeout(hostListTimer)
  detailTimer && clearTimeout(detailTimer)
  rpmTimer && clearTimeout(rpmTimer)
  hostListTimer = null
  detailTimer = null
  rpmTimer = null
}

onMounted(async () => {
  const taskId = route.query.taskId as string
  taskDetail.taskId = taskId
  taskDetail.taskType = route.query.taskType as TaskType
  if (!taskId)
    return
  await queryTaskDetailInfo(taskId)
  await queryHostInTask(taskId)
})

onBeforeUnmount(() => {
  clearTimer()
})
</script>

<template>
  <PageWrapper>
    <a-card>
      <a-spin :spinning="taskDetail.isInfoLoading">
        <div class="task-detail">
          <h1>{{ taskDetail.info.task_name }}</h1>
          <a-row type="flex">
            <a-col :span="8">
              <p>{{ t('vul.task.taskType') }}：{{ taskTypeEnum[taskDetail.taskType] }}</p>
            </a-col>
          </a-row>
          <a-row type="flex">
            <a-col :span="8">
              <p>{{ t('vul.task.taskCount') }}：{{ taskDetail.info.host_num }}</p>
            </a-col>
          </a-row>
          <a-row>
            <a-col v-if="taskDetail.info.status" :span="8">
              <a-space> {{ $t('vul.task.uptodate') }} <TaskStatus :status="taskDetail.info.status" /> </a-space>
            </a-col>
            <a-col>
              {{ t('vul.task.lastExecute') }} ：{{
                taskDetail.info.latest_execute_time
                  ? dayjs(taskDetail.info.latest_execute_time * 1000).format('YYYY-MM-DD HH:mm:ss')
                  : t('common.notExecute')
              }}
              <a v-if="taskDetail.info.latest_execute_time && !isTaskRunning" @click="toReport()">{{ $t('vul.task.viewReport') }}</a>
            </a-col>
          </a-row>
          <a-row>
            <a-col :span="8">
              <p>{{ t('vul.task.taskDesc') }}:</p>
              <p>{{ taskDetail.info.description }}</p>
            </a-col>
          </a-row>
          <a-row type="flex" justify="end">
            <a-button type="primary" :loading="isTaskRunning" @click="handleExecuteTask">
              {{
                isTaskRunning ? t('common.executing') : t('common.execute')
              }}
            </a-button>
          </a-row>
        </div>
      </a-spin>
    </a-card>

    <a-card>
      <a-row type="flex" justify="space-between">
        <a-col>
          <a-input-search v-model:value="searchKey" :maxlength="40" :placeholder="searchPlaceholder" @search="handleSearch" />
        </a-col>
        <a-col v-if="taskDetail.taskType === 'cve fix'">
          <a-button type="primary" @click="handleGenerateRollback">
            {{ $t('vul.task.generateRollback') }}
          </a-button>
        </a-col>
      </a-row>
      <a-table
        ref="tableRef"
        v-model:expandedRowKeys="tableState.expandedRowKeys"
        row-key="host_id"
        :columns="taskTableColumns"
        :data-source="tableData"
        :pagination="pagination"
        :loading="isTableLoading"
        @expand="queryRpms"
        @change="handleTableChange"
      >
        <template
          v-if="taskDetail.taskType !== 'hotpatch remove' && taskDetail.taskType !== 'repo set'"
          #expandedRowRender="{ record }"
        >
          <a-table
            :row-key="(inner:Rpm) => `${record.cve_id}-${inner.installed_rpm}-${inner.available_rpm}`"
            :columns="expandTableColumns"
            :data-source="record.rpms"
            :loading="!record.rpms"
            :pagination="false"
          >
            <template #bodyCell="{ record: expandRecord, column }">
              <template v-if="column.dataIndex === 'host_num'" />
              <template v-if="column.dataIndex === 'status'">
                <a-badge :status="statusValueMap[expandRecord.status]" />
                {{ statusEnumMap[taskDetail.taskType][expandRecord.status] }}
              </template>
              <template v-if="column.dataIndex === 'cves'">
                <a-tooltip placement="top">
                  <template #title>
                    <span>{{ expandRecord.cves }}</span>
                  </template>
                  {{ cutOff(expandRecord.cves) }}
                </a-tooltip>
              </template>
            </template>
          </a-table>
        </template>
        <template #bodyCell="{ record, column }">
          <template v-if="column.dataIndex === 'repo_name'">
            {{ record.repo_name || '————' }}
          </template>
          <template v-if="column.dataIndex === 'status'">
            <a-badge :status="statusValueMap[record.status]" />
            {{ statusEnumMap[taskDetail.taskType][record.status] }}
          </template>
          <template v-if="column.dataIndex === 'progress'">
            <a-progress v-if="record.status === 'succeed'" :percent="100" />
            <a-progress v-if="record.status === 'running'" :percent="50" status="active" />
            <a-progress v-if="record.status === 'fail'" :percent="70" status="exception" />
            <a-progress v-if="record.status === 'unknown'" :percent="0" />
          </template>
        </template>
      </a-table>
    </a-card>

    <TaskModal v-model:visible="isSuccessModalVisible" :title="$t('vul.task.sentence.rollbackSucceed')" />
  </PageWrapper>
</template>

<style lang="less" scoped>
.task-detail {
  padding: 0 20px;
  h1 {
    font-size: 23px;
    font-weight: bold;
  }
}
</style>
